<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GoYOLO API 测试页面</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        
        .header p {
            font-size: 1.1em;
            opacity: 0.9;
        }
        
        .content {
            padding: 30px;
        }
        
        .api-section {
            margin-bottom: 30px;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 20px;
            background: #f9f9f9;
        }
        
        .api-section h2 {
            color: #667eea;
            margin-bottom: 15px;
            font-size: 1.5em;
            border-bottom: 2px solid #667eea;
            padding-bottom: 10px;
        }
        
        .form-group {
            margin-bottom: 15px;
        }
        
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: 600;
            color: #333;
        }
        
        input[type="text"],
        input[type="file"],
        textarea {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: monospace;
            font-size: 0.9em;
        }
        
        textarea {
            min-height: 100px;
            resize: vertical;
        }
        
        button {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1em;
            font-weight: 600;
            transition: transform 0.2s, box-shadow 0.2s;
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
        }
        
        button:active {
            transform: translateY(0);
        }
        
        .response {
            margin-top: 15px;
            padding: 15px;
            background: white;
            border: 1px solid #ddd;
            border-radius: 4px;
            max-height: 300px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 0.85em;
            white-space: pre-wrap;
            word-wrap: break-word;
        }
        
        .response.success {
            border-left: 4px solid #4caf50;
            background: #f1f8f4;
        }
        
        .response.error {
            border-left: 4px solid #f44336;
            background: #fef5f5;
        }
        
        .response.loading {
            border-left: 4px solid #2196f3;
            background: #f5f9ff;
        }
        
        .grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }
        
        @media (max-width: 768px) {
            .grid {
                grid-template-columns: 1fr;
            }
        }
        
        .status-badge {
            display: inline-block;
            padding: 5px 10px;
            border-radius: 20px;
            font-size: 0.85em;
            font-weight: 600;
            margin-top: 10px;
        }
        
        .status-badge.online {
            background: #4caf50;
            color: white;
        }
        
        .status-badge.offline {
            background: #f44336;
            color: white;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>🚀 GoYOLO API 测试工具</h1>
            <p>服务地址: <strong>http://192.168.10.11:8080</strong></p>
            <div id="status" class="status-badge offline">检查中...</div>
        </div>
        
        <div class="content">
            <!-- 获取模型信息 -->
            <div class="api-section">
                <h2>📋 获取模型信息</h2>
                <p style="margin-bottom: 15px; color: #666;">GET /api/v1/model/info</p>
                <button onclick="getModelInfo()">获取模型信息</button>
                <div id="modelInfoResponse" class="response"></div>
            </div>
            
            <!-- 推理接口 -->
            <div class="api-section">
                <h2>🔍 单图推理</h2>
                <p style="margin-bottom: 15px; color: #666;">POST /api/v1/infer</p>
                <div class="form-group">
                    <label>图像路径:</label>
                    <textarea id="inferInput" placeholder="输入图像的完整路径，例如: /path/to/image.jpg 或 /opt/goyolo/models/test.jpg"></textarea>
                </div>
                <p style="font-size: 12px; color: #999; margin-top: 5px;">
                    💡 提示: 输入服务器上存在的图像文件路径
                </p>
                <button onclick="infer()">执行推理</button>
                <div id="inferResponse" class="response"></div>
            </div>
            
            <!-- 批量推理 -->
            <div class="api-section">
                <h2>📦 批量推理</h2>
                <p style="margin-bottom: 15px; color: #666;">POST /api/v1/infer/batch</p>
                <div class="form-group">
                    <label>图像路径列表 (JSON 数组):</label>
                    <textarea id="batchInput" placeholder='输入 JSON 数组，例如: ["/path/to/image1.jpg", "/path/to/image2.jpg"]'></textarea>
                </div>
                <p style="font-size: 12px; color: #999; margin-top: 5px;">
                    💡 提示: 输入服务器上存在的图像文件路径数组
                </p>
                <button onclick="inferBatch()">执行批量推理</button>
                <div id="batchResponse" class="response"></div>
            </div>
            
            <!-- 上传并推理 -->
            <div class="api-section">
                <h2>📤 上传并推理</h2>
                <p style="margin-bottom: 15px; color: #666;">POST /api/v1/infer/upload</p>
                <div class="form-group">
                    <label>选择图像文件:</label>
                    <input type="file" id="uploadFile" accept="image/*">
                </div>
                <button onclick="uploadAndInfer()">上传并推理</button>
                <div id="uploadResponse" class="response"></div>
            </div>
            
            <!-- 文本检测 -->
            <div class="api-section">
                <h2>📝 文本检测</h2>
                <p style="margin-bottom: 15px; color: #666;">POST /api/v1/detect/text</p>
                <div class="form-group">
                    <label>图像路径:</label>
                    <textarea id="textDetectImagePath" placeholder="输入图像的完整路径，例如: /path/to/image.jpg"></textarea>
                </div>
                <div class="form-group">
                    <label>输出目录:</label>
                    <textarea id="textDetectOutputDir" placeholder="输入输出目录路径，例如: /tmp/text_regions"></textarea>
                </div>
                <div class="form-group">
                    <label>置信度阈值 (0.0-1.0):</label>
                    <input type="number" id="textDetectConfidence" min="0" max="1" step="0.1" value="0.5" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
                </div>
                <div class="form-group">
                    <label>
                        <input type="checkbox" id="textDetectSaveRegions"> 保存检测到的文本区域
                    </label>
                </div>
                <p style="font-size: 12px; color: #999; margin-top: 5px;">
                    💡 提示: 输入服务器上存在的图像路径和输出目录
                </p>
                <button onclick="detectText()">检测文本</button>
                <div id="textDetectResponse" class="response"></div>
            </div>
        </div>
    </div>
    
    <script>
        const API_BASE = 'http://192.168.10.11:8080';

        // 创建带 CORS 处理的 fetch 请求
        async function fetchWithCORS(url, options = {}) {
            const defaultOptions = {
                method: 'GET',
                mode: 'cors',
                credentials: 'omit'
            };

            // 只有在不是 FormData 时才设置 Content-Type
            if (!(options.body instanceof FormData)) {
                defaultOptions.headers = {
                    'Content-Type': 'application/json',
                };
            }

            const finalOptions = {
                ...defaultOptions,
                ...options,
                headers: {
                    ...(defaultOptions.headers || {}),
                    ...(options.headers || {})
                }
            };

            try {
                const response = await fetch(url, finalOptions);
                return response;
            } catch (error) {
                console.error('CORS Error:', error);
                throw error;
            }
        }

        // 检查服务状态
        async function checkStatus() {
            try {
                const response = await fetchWithCORS(`${API_BASE}/api/v1/model/info`, {
                    method: 'GET'
                });
                const statusBadge = document.getElementById('status');
                if (response.ok) {
                    statusBadge.textContent = '✓ 在线';
                    statusBadge.className = 'status-badge online';
                } else {
                    statusBadge.textContent = '✗ 离线';
                    statusBadge.className = 'status-badge offline';
                }
            } catch (error) {
                document.getElementById('status').textContent = '✗ 离线';
                document.getElementById('status').className = 'status-badge offline';
            }
        }
        
        // 获取模型信息
        async function getModelInfo() {
            const responseDiv = document.getElementById('modelInfoResponse');
            responseDiv.className = 'response loading';
            responseDiv.textContent = '加载中...';

            try {
                const response = await fetchWithCORS(`${API_BASE}/api/v1/model/info`, {
                    method: 'GET'
                });
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                const data = await response.json();
                responseDiv.className = 'response success';
                responseDiv.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                responseDiv.className = 'response error';
                responseDiv.textContent = `错误: ${error.message}\n\n提示: 如果出现 CORS 错误，请确保后端已配置 CORS 中间件。`;
            }
        }
        
        // 单图推理
        async function infer() {
            const input = document.getElementById('inferInput').value.trim();
            if (!input) {
                alert('请输入图像路径');
                return;
            }

            const responseDiv = document.getElementById('inferResponse');
            responseDiv.className = 'response loading';
            responseDiv.textContent = '推理中...';

            try {
                const response = await fetchWithCORS(`${API_BASE}/api/v1/infer`, {
                    method: 'POST',
                    body: JSON.stringify({ image_path: input })
                });
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                const data = await response.json();
                responseDiv.className = 'response success';
                responseDiv.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                responseDiv.className = 'response error';
                responseDiv.textContent = `错误: ${error.message}\n\n提示: 请确保:\n1. 图像路径正确\n2. 文件存在于服务器\n3. 后端已配置 CORS 中间件`;
            }
        }
        
        // 批量推理
        async function inferBatch() {
            const input = document.getElementById('batchInput').value.trim();
            if (!input) {
                alert('请输入图像列表');
                return;
            }

            const responseDiv = document.getElementById('batchResponse');
            responseDiv.className = 'response loading';
            responseDiv.textContent = '推理中...';

            try {
                const imagePaths = JSON.parse(input);
                if (!Array.isArray(imagePaths)) {
                    throw new Error('输入必须是数组格式');
                }
                const response = await fetchWithCORS(`${API_BASE}/api/v1/infer/batch`, {
                    method: 'POST',
                    body: JSON.stringify({ image_paths: imagePaths })
                });
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                const data = await response.json();
                responseDiv.className = 'response success';
                responseDiv.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                responseDiv.className = 'response error';
                responseDiv.textContent = `错误: ${error.message}\n\n提示: 请确保:\n1. 输入是有效的 JSON 数组\n2. 图像路径正确\n3. 文件存在于服务器`;
            }
        }
        
        // 上传并推理
        async function uploadAndInfer() {
            const fileInput = document.getElementById('uploadFile');
            if (!fileInput.files.length) {
                alert('请选择图像文件');
                return;
            }

            const file = fileInput.files[0];
            const responseDiv = document.getElementById('uploadResponse');
            responseDiv.className = 'response loading';
            responseDiv.textContent = `上传并推理中...\n文件: ${file.name}\n大小: ${(file.size / 1024 / 1024).toFixed(2)} MB`;

            try {
                const formData = new FormData();
                // 注意: 后端期望的字段名是 "image"，不是 "file"
                formData.append('image', file);

                const response = await fetchWithCORS(`${API_BASE}/api/v1/infer/upload`, {
                    method: 'POST',
                    body: formData
                    // 不要设置 headers，FormData 会自动设置正确的 Content-Type
                });

                const data = await response.json();

                if (!response.ok) {
                    const errorMsg = data.error || response.statusText;
                    throw new Error(`HTTP ${response.status}: ${errorMsg}`);
                }

                responseDiv.className = 'response success';
                responseDiv.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
            } catch (error) {
                console.error('上传并推理错误:', error);
                responseDiv.className = 'response error';
                responseDiv.innerHTML = `
                    <strong>❌ 错误: ${error.message}</strong><br><br>
                    <strong>故障排除步骤:</strong><br>
                    <small>
                    1. ✓ 确认选择了有效的图像文件 (JPG, PNG 等)<br>
                    2. ✓ 确认文件大小合理 (< 50MB)<br>
                    3. ✓ 确认后端已配置 CORS 中间件<br>
                    4. ✓ 检查模型文件是否存在: /opt/goyolo/models/yolov8n.xml<br>
                    5. ✓ 查看服务日志: sudo journalctl -u goyolo -f<br>
                    6. ✓ 手动测试: curl -F 'image=@/path/to/image.jpg' http://192.168.10.11:8080/api/v1/infer/upload
                    </small>
                `;
            }
        }
        
        // 文本检测
        async function detectText() {
            const imagePath = document.getElementById('textDetectImagePath').value.trim();
            const outputDir = document.getElementById('textDetectOutputDir').value.trim();
            const confidence = parseFloat(document.getElementById('textDetectConfidence').value);
            const saveRegions = document.getElementById('textDetectSaveRegions').checked;

            if (!imagePath) {
                alert('请输入图像路径');
                return;
            }
            if (!outputDir) {
                alert('请输入输出目录');
                return;
            }
            if (isNaN(confidence) || confidence < 0 || confidence > 1) {
                alert('置信度必须在 0.0 到 1.0 之间');
                return;
            }

            const responseDiv = document.getElementById('textDetectResponse');
            responseDiv.className = 'response loading';
            responseDiv.textContent = '检测中...';

            try {
                const response = await fetchWithCORS(`${API_BASE}/api/v1/detect/text`, {
                    method: 'POST',
                    body: JSON.stringify({
                        image_path: imagePath,
                        output_dir: outputDir,
                        confidence_threshold: confidence,
                        save_regions: saveRegions
                    })
                });
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                const data = await response.json();
                responseDiv.className = 'response success';
                responseDiv.textContent = JSON.stringify(data, null, 2);
            } catch (error) {
                responseDiv.className = 'response error';
                responseDiv.textContent = `错误: ${error.message}\n\n提示: 请确保:\n1. 图像路径正确\n2. 输出目录存在\n3. 文件权限正确`;
            }
        }
        
        // 页面加载时检查状态
        window.addEventListener('load', () => {
            checkStatus();
            setInterval(checkStatus, 5000); // 每5秒检查一次
        });
    </script>
</body>
</html>

